﻿@page "/gateway/router"
@inject IApiGateway iApiGateway


<Button OnClick="AddItem" Type="primary" Style="margin-bottom:16px">
    Add a route
</Button>
<Table TItem="Data"
       DataSource="@data"
       @bind-PageIndex="pagination.page"
       @bind-PageSize="pagination.rows"
       Total="pagination.records"
       Loading="_loading" OnChange="HandleTableChange" RemoteDataSource>
    <Column @bind-Field="@context.ReciveRoute"></Column>
    <Column @bind-Field="@context.ToRoute"></Column>
    <Column @bind-Field="@context.Methods">
        @foreach (var tag in context.Methods)
        {
            var color = "green";
            <Tag Color="@color">@tag</Tag>
        }
    </Column>
    <Column @bind-Field="@context.CacheTime"></Column>
    <Column @bind-Field="@context.Ops"></Column>
    <ActionColumn Title="操作">
        <Space Size=@("middle")>
            <SpaceItem>
                <Button Type="@ButtonType.Link" Block OnClick="_ => EditItem(context)">
                    Edit
                </Button>
            </SpaceItem>
            <SpaceItem>
                <Popconfirm Title="Are you sure delete this route?"
                            OnConfirm="_ => DeleteItem(context)"
                            OkText="Yes"
                            CancelText="No">
                    <Button Type="@ButtonType.Link" Danger>
                        Delete
                    </Button>
                </Popconfirm>

            </SpaceItem>
        </Space>
    </ActionColumn>
</Table>


<Drawer Closable="true" Width="800" Visible="visible" Title=DrawerTitle OnClose="_=>closeDrawer()">
    <Template style="height:90%">
        <Form Model="@GatewayRoutesOptions"
              LabelColSpan="8"
              WrapperColSpan="16"
              OnFinish="OnFinish"
              Size="@AntSizeLDSType.Default">

            <FormItem Label="Match Route">
                <Input @bind-Value="@context.UpstreamPathTemplate" />
            </FormItem>

            <FormItem Label="Methods">
                <Select Mode="multiple" DataSource="@httpMethods"
                        @bind-Values="@context.UpstreamHttpMethod"
                        LabelName="@nameof(httpMethodSelectItem.Name)"
                        ValueName="@nameof(httpMethodSelectItem.Type)">
                </Select>
            </FormItem>

            @*转发目标配置*@
            <FormItem Label="To Schema">
                <RadioGroup @bind-Value="@context.DownstreamScheme" onChange="@schemaChange(context)">
                    <Radio RadioButton Value='("http")'>http</Radio>
                    <Radio RadioButton Value='("https")'>https</Radio>
                </RadioGroup>
            </FormItem>

            @for (int i = 0; i < context.DownstreamHostAndPorts.Count; i++)
            {
                var item = context.DownstreamHostAndPorts[i];

                <FormItem Label="@("To Serve - "+(i+1))">
                    <InputGroup>
                        <Row Gutter="8">
                            <div style="width:50%; float:left;">
                                <Input Style="border-radius: 2px 0 0 2px; border-right: none;" Placeholder="Serve 例：127.0.0.1" @bind-Value="@item.Host" />
                            </div>
                            <div style="width:18%; float:left;">
                                <Input Style="        border-radius: 0 2px 2px 0;" Placeholder="Port 例:80" @bind-Value="@item.Port" />
                            </div>
                            <div class="add-icon" style="float:left;" @onclick="_ =>
                                                                                {
                                                                                    if (context.DownstreamHostAndPorts.Count > 1)
                                                                                    {
                                                                                        context.DownstreamHostAndPorts.Remove(item);
                                                                                    }
                                                                                }">
                                <Icon Type="minus" Theme="outline" Style="font-size:16px" />
                            </div>
                        </Row>
                    </InputGroup>
                </FormItem>
            }

            <FormItem Label="To Route Rule">
                <Input @bind-Value="@context.DownstreamPathTemplate" />
            </FormItem>

            <FormItem WrapperColOffset="8" WrapperColSpan="16">
                <Button @onclick="AddDownstreamHostAndPorts">Add Serve</Button>
            </FormItem>

            <FormItem Label="Request cache">
                <AntDesign.Input Placeholder="请求缓存时间/单位S，不启用则忽略" @bind-Value="@context.FileCacheOptions.TtlSeconds">
                    <AddOnAfter>s</AddOnAfter>
                </AntDesign.Input>
            </FormItem>

            <FormItem Label="负载均衡">
                <RadioGroup @bind-Value="@context.LoadBalancerOptions.Type">
                    <Radio RadioButton Value="@(LoadBalancerEnum.RoundRobin)">轮询</Radio>
                    <Radio RadioButton Value="@(LoadBalancerEnum.LeastConnection)">最少负担</Radio>
                    <Radio RadioButton Value="@(LoadBalancerEnum.NoLoadBalancer)">第一个可用</Radio>
                </RadioGroup>
            </FormItem>

            <FormItem Label="流量限制">
                <RadioGroup @bind-Value="@context.RateLimitOptions.EnableRateLimiting">
                    <Radio Value="true">启用</Radio>
                    <Radio Value="false">禁用</Radio>
                </RadioGroup>
                <div Style="margin-top:10px">
                    <Input Disabled="@(context.RateLimitOptions.EnableRateLimiting == false)" Placeholder="限制适用的范围，例如1s，5m，1h，1d等" @bind-Value="@context.RateLimitOptions.Period" />
                </div>
                <div Style="margin-top:10px">
                    <Input Disabled="@(context.RateLimitOptions.EnableRateLimiting == false)" Placeholder="限制时间内，指定客户端最大请求数量" @bind-Value="@context.RateLimitOptions.Limit" />
                </div>
                <div Style="margin-top:10px">
                    <AntDesign.Input Disabled="@(context.RateLimitOptions.EnableRateLimiting == false)" Placeholder="超出可以在一定秒数后重试" @bind-Value="@context.RateLimitOptions.PeriodTimespan">
                        <AddOnAfter>s</AddOnAfter>
                    </AntDesign.Input>
                </div>
            </FormItem>

            <FormItem WrapperColOffset="8" WrapperColSpan="16">
                <Button HtmlType="submit">
                    Submit
                </Button>
            </FormItem>
        </Form>
    </Template>
</Drawer>

<style>
    .ant-form-item-label {
        flex: 0 0 130px !important;
    }

    .ant-col-offset-8 {
        margin-left: 130px;
    }


    .add-icon {
        border: 1px solid #abaaaa;
        color: #797777;
        border-radius: 50%;
        width: 24px;
        height: 24px;
        text-align: center;
        line-height: 24px;
        margin-top: 4px;
        margin-left: 20px;
        padding-right: 1px;
        cursor:pointer;
    }
</style>

@code {
    WindowsHelper windows;
    Core.DataAccess.Model.Pagination pagination = new Core.DataAccess.Model.Pagination
    {
        page = 1,
        rows = 7
    };
    bool _loading = false;
    bool visible = false;
    string DrawerTitle = "Add Route";
    int selectRowId = 0;


    GatewayRoutesOptions GatewayRoutesOptionsDefault = new GatewayRoutesOptions
    {
        DownstreamHostAndPorts = new List<DownstreamHostAndPort>
    {
            new DownstreamHostAndPort {
             Port = 80
            }
        },
        LoadBalancerOptions = new LoadBalancerOptions(),
        FileCacheOptions = new FileCacheOptions(),
        RateLimitOptions = new RouteRateLimitOptions(),
        QoSOptions = null,
        UpstreamHttpMethod = new List<HandlerContextOptions.HttpMethod> {
            HandlerContextOptions.HttpMethod.GET,
            HandlerContextOptions.HttpMethod.POST
        }
    };

    GatewayRoutesOptions GatewayRoutesOptions = new GatewayRoutesOptions
    {
        DownstreamHostAndPorts = new List<DownstreamHostAndPort>
    {
            new DownstreamHostAndPort {
             Port = 80
            }
        },
        LoadBalancerOptions = new LoadBalancerOptions(),
        FileCacheOptions = new FileCacheOptions(),
        RateLimitOptions = new RouteRateLimitOptions(),
        QoSOptions = null,
        UpstreamHttpMethod = new List<HandlerContextOptions.HttpMethod> {
            HandlerContextOptions.HttpMethod.GET,
            HandlerContextOptions.HttpMethod.POST
        }
    };

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            windows = new WindowsHelper(jSRuntime);
            await this.LoadData();
            await windows.HideLoadingAsync();
            await windows.SetTitleAsync("Router Edit");
        }
    }


    private object schemaChange(GatewayRoutesOptions options)
    {
        foreach (var item in options.DownstreamHostAndPorts)
        {
            item.Port = options.DownstreamScheme == "http" ? 80 : 443;
        }
        return null;
    }

    private async Task LoadData()
    {
        this._loading = true;
        await base.InvokeAsync(async () =>
        {
            this.data = new List<Data>();
            var result = await this.iApiGateway.GetRoutes(pagination);
            await windows.ConsoleLogAsync(result);
            foreach (var item in result)
            {
                this.data.Add(this.GetGatewayRouteFormat(item));
            }
            this._loading = false;
            base.StateHasChanged();
        });
    }

    private async Task DeleteItem(Data item)
    {
        await InvokeAsync(async () =>
        {
            if (await this.iApiGateway.RemoveRoute(item.Id))
            {
                this.data.Remove(item);
                this.GatewayRoutesOptions.Ini();
                base.StateHasChanged();
            }
            else
            {
                await notice.Error(new NotificationConfig
                {
                    Message = "操作失败请重试"
                });
            }
        });
    }

    private async Task HandleTableChange(QueryModel<Data> queryModel)
    {
        if (windows != null)
        {
            this.pagination.page = queryModel.PageIndex;
            this.pagination.rows = queryModel.PageSize;
            await this.LoadData();
        }
    }

    void AddItem()
    {
        this.GatewayRoutesOptions.Ini();
        this.DrawerTitle = "Add Route";
        this.selectRowId = 0;
        this.visible = true;
    }

    void EditItem(Data item)
    {
        this.GatewayRoutesOptions = item.GatewayRoutesOptions;
        this.DrawerTitle = "Edit Route";
        this.selectRowId = item.Id;
        this.visible = true;
    }

    void closeDrawer()
    {
        this.visible = false;
        this.DrawerTitle = "Add Route";
        this.selectRowId = 0;
    }

    void AddDownstreamHostAndPorts()
    {
        GatewayRoutesOptions.DownstreamHostAndPorts.Add(new DownstreamHostAndPort());
    }

    void RemoveDownstreamHostAndPorts(int index)
    {
        GatewayRoutesOptions.DownstreamHostAndPorts.RemoveAt(index);
    }

    Data GetGatewayRouteFormat(GatewayRoute item)
    {
        var config = item.RouteConfig.TryToEntity<GatewayRoutesOptions>();
        var result = new Data
        {
            GatewayRoutesOptions = config,
            Id = item.RouteId
        };
        result.Changed();
        return result;
    }
}


@code{
    class httpMethodSelectItem
    {
        public httpMethodSelectItem(string name, HandlerContextOptions.HttpMethod type)
        {
            this.Type = type;
            this.Name = name;
        }
        public string Name { get; set; }
        public HandlerContextOptions.HttpMethod Type { get; set; }
    }

    private List<httpMethodSelectItem> httpMethods = new List<httpMethodSelectItem>
{
        new httpMethodSelectItem("GET",HandlerContextOptions.HttpMethod.GET),
        new httpMethodSelectItem("POST",HandlerContextOptions.HttpMethod.POST),
        new httpMethodSelectItem("PUT",HandlerContextOptions.HttpMethod.PUT),
        new httpMethodSelectItem("DELETE",HandlerContextOptions.HttpMethod.DELETE),
        new httpMethodSelectItem("OPTIONS",HandlerContextOptions.HttpMethod.OPTIONS)
    };

    private async void OnFinish(EditContext editContext)
    {
        await windows.ConsoleErrorAsync(editContext);
        if (await this.DataValidation())
        {
            bool isSuccess = false;
            string merrorMessage = string.Empty;
            // submit
            await base.InvokeAsync(async () =>
            {
                if (selectRowId == 0)
                {
                    var result = await this.iApiGateway.AddRoute(this.GatewayRoutesOptions);
                    isSuccess = result.RouteId > 0;
                    if (isSuccess)
                    {
                        await windows.ConsoleLogAsync(result);
                        this.data.Add(this.GetGatewayRouteFormat(result));
                        pagination.records++;
                    }
                    else
                    {
                        merrorMessage = result.RouteConfig;
                    }
                }
                else
                {
                    var result = await this.iApiGateway.EditRoute(selectRowId, this.GatewayRoutesOptions);
                    isSuccess = result.RouteId > 0;

                    foreach (var item in this.data)
                    {
                        if (item.Id == selectRowId)
                        {
                            item.Changed();
                            break;
                        }
                    }
                }

                if (isSuccess)
                {
                    this.closeDrawer();
                    await windows.ConsoleLogAsync(this.GatewayRoutesOptions);
                    this.GatewayRoutesOptions = GatewayRoutesOptionsDefault;

                    // 更新UI
                    base.StateHasChanged();

                    var task = notice.Success(new NotificationConfig
                    {
                        Message = "保存成功"
                    });
                }
                else
                {
                    var task = notice.Error(new NotificationConfig
                    {
                        Message = "操作失败请重试",
                        Description = merrorMessage
                    });
                }
            });
        }


    }

    private async Task<bool> DataValidation()
    {
        var message = new NotificationConfig
        {
            Message = "数据验证失败"
        };

        if (string.IsNullOrWhiteSpace(this.GatewayRoutesOptions.DownstreamPathTemplate))
        {
            message.Description = "To Route Rule is null";
        }

        if (string.IsNullOrWhiteSpace(this.GatewayRoutesOptions.UpstreamPathTemplate))
        {
            message.Description = "To Route Rule is null";
        }

        if (this.GatewayRoutesOptions.UpstreamHttpMethod.Count() == 0)
        {
            message.Description = "Methods is null";
        }

        if (this.GatewayRoutesOptions.DownstreamHostAndPorts.Count() == 0)
        {
            message.Description = "To Serve is null";
        }
        else
        {
            for (int i = 0; i < this.GatewayRoutesOptions.DownstreamHostAndPorts.Count(); i++)
            {
                var item = this.GatewayRoutesOptions.DownstreamHostAndPorts[i];
                int port = 0; int.TryParse(item.Port.ToString(), out port);

                if (port == 0)
                {
                    message.Description = $"To Serve-{(i + 1)} port is null";
                }

                if (string.IsNullOrWhiteSpace(item.Host))
                {
                    message.Description = $"To Serve-{(i + 1)} host is null";
                }
                if (message.Description.Value != null)
                {
                    break;
                }
            }
        }


        if (this.GatewayRoutesOptions.RateLimitOptions.EnableRateLimiting)
        {
            if (!string.IsNullOrWhiteSpace(this.GatewayRoutesOptions.RateLimitOptions.Period))
            {
                var isUnitSpecifications = "smhd".ToCharArray().Select(item => this.GatewayRoutesOptions.RateLimitOptions.Period.EndsWith(item)).Any(item => item);
                if (!isUnitSpecifications)
                {
                    message.Description = "流量限制范围单位不合规， 请使用： s,m,h,d 结尾";
                }
                else
                {
                    var length = this.GatewayRoutesOptions.RateLimitOptions.Period.TrimEnd("smhd".ToCharArray());
                    int.TryParse(length, out int count);
                    if (count == 0)
                    {
                        message.Description = "流量限制范围时间不合规， 必须大于 >0";
                    }
                }
            }
            else
            {
                message.Description = "流量限制范围不合规， 请指定适用范围";
            }

            if (this.GatewayRoutesOptions.RateLimitOptions.Limit < 1)
            {
                message.Description = "流量限制范围不合规， 请限制时间内，指定客户端最大请求数量";
            }

            if (this.GatewayRoutesOptions.RateLimitOptions.PeriodTimespan < 1)
            {
                this.GatewayRoutesOptions.RateLimitOptions.PeriodTimespan = 1;
            }
        }

        if (message.Description.Value != null)
        {
            await notice.Error(message);
            return false;
        }


        return true;

    }
}





@code{

    List<Data> data = new List<Data>();

    public class Data
    {
        public int Id { get; set; }
        [DisplayName("接收路由")]
        public string ReciveRoute { get; set; }
        [DisplayName("转发路由")]
        public string ToRoute { get; set; }
        [DisplayName("支持谓词")]
        public string[] Methods { get; set; }
        [DisplayName("缓存时间")]
        public string CacheTime { get; set; }
        [DisplayName("流量限制")]
        public string Ops { get; set; }

        public GatewayRoutesOptions GatewayRoutesOptions { get; set; }

        public void Changed()
        {
            var config = this.GatewayRoutesOptions;
            this.ReciveRoute = config.UpstreamPathTemplate;
            this.ToRoute = config.DownstreamScheme + "://" + string.Join('-', config.DownstreamHostAndPorts.Select(router => router.Host + ":" + router.Port)) + config.DownstreamPathTemplate;
            this.CacheTime = config.FileCacheOptions.TtlSeconds == null ? "not use" : config.FileCacheOptions.TtlSeconds + "s";
            this.Methods = config.UpstreamHttpMethod.Select(method => method.ToString()).ToArray();
            this.Ops = config.RateLimitOptions.EnableRateLimiting ?
                    $"count:{config.RateLimitOptions.Limit}/{config.RateLimitOptions.Period}, retry:{config.RateLimitOptions.PeriodTimespan}s" : "not use";
        }
    }
}


